﻿using System;
using System.Text;
using System.Text.RegularExpressions;
using DimensionsHelper.Common.Contracts.Excel;

namespace DimensionsHelper.Common.Utils;

public static class ExcelHelper
{
    public const int MaxRow = 1048576;
    public const int MaxCol = 16384;

    public static bool IsRowValid(int row)
    {
        return row is >= 1 and <= MaxRow;
    }

    public static bool IsColumnValid(int column)
    {
        return column is >= 1 and <= MaxCol;
    }
    
    
    private static Regex? _validateExcelSheetNameRegex;
    private static Regex ValidateExcelSheetNameRegex => _validateExcelSheetNameRegex ??= new Regex(@":|\?|/|\\|\[|\]");
    

    private static bool ValidateExcelSheetName(string sheetName)
    {
        return ValidateExcelSheetNameRegex.IsMatch(sheetName);
    }

    public static string GetFixedExcelSheetName(string sheetName, Func<string, bool>? fetch = null)
    {
        if (string.IsNullOrEmpty(sheetName) || sheetName.Trim() == "")
        {
            throw new ArgumentException("工作表名不能为空。");
        }

        if (ValidateExcelSheetName(sheetName))
        {
            sheetName = Regex.Replace(sheetName, @":|\?|/|\\|\[|\]", " ");
        }

        if (sheetName.StartsWith("'") || sheetName.EndsWith("'"))
        {
            throw new ArgumentException("工作表名不能以单引号开头或结尾。");
        }

        if (sheetName.Length <= 31)
        {
            return sheetName;
        }

        sheetName = sheetName.Substring(0, 31);

        if (fetch == null || !fetch(sheetName))
        {
            return sheetName;
        }

        sheetName = sheetName.Substring(0, sheetName.Length - 3);
        int c = 1;
        while (true)
        {
            sheetName += "(" + c + ")";
            if (!fetch(sheetName))
            {
                return sheetName;
            }

            c++;
        }
    }

    private static string GetExcelColumnLetter(int column)
    {
        if (column < 1)
        {
            throw new Exception("列无效。");
        }

        var result = new StringBuilder();

        while (column > 0)
        {
            int index = (column - 1) % 26;
            result.Insert(0, (char)('A' + index));
            column = (column - 1) / 26;
        }

        return result.ToString();
    }

    public static string GetExcelAddress(int row, int column, bool isAbsolutely)
    {
        string columnName = GetExcelColumnLetter(column);

        if (isAbsolutely)
        {
            return $"${columnName}${row}";
        }

        return columnName + row;
    }

    public static string GetExcelAddress(int fromRow, int fromColumn, int toRow, int toColumn, bool isAbsolutely)
    {
        if (!IsRowValid(fromRow))
        {
            throw new ArgumentOutOfRangeException(nameof(fromRow));
        }

        if (!IsRowValid(toRow))
        {
            throw new ArgumentOutOfRangeException(nameof(toRow));
        }

        if (!IsColumnValid(fromColumn))
        {
            throw new ArgumentOutOfRangeException(nameof(fromColumn));
        }

        if (!IsColumnValid(toColumn))
        {
            throw new ArgumentOutOfRangeException(nameof(toColumn));
        }

        if (fromRow == toRow && fromColumn == toColumn)
        {
            return GetExcelAddress(fromRow, fromColumn, isAbsolutely);
        }

        string text = isAbsolutely ? "$" : "";

        if (fromRow == 1 && toRow == MaxRow)
        {
            return $"{text}{GetExcelColumnLetter(fromColumn)}:{text}{GetExcelColumnLetter(toColumn)}";
        }

        if (fromColumn == 1 && toColumn == MaxCol)
        {
            return $"{text}{fromRow}:{text}{toRow}";
        }

        return GetExcelAddress(fromRow, fromColumn, isAbsolutely) + ":" +
               GetExcelAddress(toRow, toColumn, isAbsolutely);
    }

    public static string GetExcelColumnsAddress(int fromColumn, int toColumn)
    {
        return $"{GetExcelColumnLetter(fromColumn)}:{GetExcelColumnLetter(toColumn)}";
    }

    public static string GetExcelR1C1Address(int row, int column)
    {
        return "R" + row + "C" + column;
    }

    private static int GetColumnNumberFromName(string columnName)
    {
        int result = 0;
        int p = 1;

        for (int i = columnName.Length - 1; i >= 0; i--)
        {
            char ch = columnName[i];
            result += (ch - 'A' + 1) * p;
            p *= 26;
        }

        return result;
    }

    private static bool TryGetR1C1AddressRowColumn(string address, out int row, out int col)
    {
        row = 0;
        col = 0;

        var r1C1Regex = new Regex(@"^R([0-9]+)C([0-9]+)$");
        Match match = r1C1Regex.Match(address);

        if (!match.Success)
        {
            return false;
        }

        row = int.Parse(match.Groups[1].Value);
        col = int.Parse(match.Groups[2].Value);
        return true;
    }

    public static Dimension GetDimension(string address)
    {
        address = address.ToUpper();
        CellPosition start;

        if (TryGetR1C1AddressRowColumn(address, out int row, out int col))
        {
            start = new CellPosition(row, col);
            return new Dimension
            {
                Start = start,
                End = start
            };
        }

        var addressRegex = new Regex(@"\$?([A-Z]+)\$?([0-9]+)\s*(?::\s*\$?([A-Z]+)\$?([0-9]+)\s*)?");
        Match match = addressRegex.Match(address);

        if (!match.Success)
        {
            throw new Exception("无效的地址字符串。");
        }

        if (match.Groups.Count < 3 || string.IsNullOrEmpty(match.Groups[1].Value) ||
            string.IsNullOrEmpty(match.Groups[2].Value))
        {
            throw new Exception("无效的地址字符串。");
        }

        start = new CellPosition(int.Parse(match.Groups[2].Value), 
            GetColumnNumberFromName(match.Groups[1].Value));
        if (string.IsNullOrEmpty(match.Groups[3].Value) || string.IsNullOrEmpty(match.Groups[4].Value))
        {
            return new Dimension { Start = start, End = start };
        }

        var end = new CellPosition(int.Parse(match.Groups[4].Value),
            GetColumnNumberFromName(match.Groups[3].Value));
        return new Dimension { Start = start, End = end };
    }

    public static int GetExcelRId(string rId)
    {
        if (rId.Length < 4 || !rId.StartsWith("rId") || !int.TryParse(rId.Substring(3), out int result))
        {
            throw new Exception("无效的rId");
        }

        return result;
    }

    public static string MakeExcelRId(int rId)
    {
        return "rId" + rId;
    }

    public static double CalculateColumnWidth(double fontMaxNumberPixelWidth, int length)
    {
        return Math.Truncate(((length * fontMaxNumberPixelWidth) + 5) / fontMaxNumberPixelWidth * 256) / 256;
    }

    public static int CalculateColumnPixelWidth(double columnWidth, double fontMaxNumberPixelWidth)
    {
        return Convert.ToInt32(Math.Truncate(((256 * columnWidth) + Math.Truncate(128 / fontMaxNumberPixelWidth)) /
            256 * fontMaxNumberPixelWidth));
    }

    public static int CalculateColumnFontLength(double columnPixelWidth, double fontMaxNumberPixelWidth)
    {
        return Convert.ToInt32(Math.Truncate(((columnPixelWidth - 5) / fontMaxNumberPixelWidth * 100) + 0.5) / 100);
    }

    public static string UnderLineTypeToOpenXmlValue(UnderLineType type)
    {
        switch (type)
        {
            case UnderLineType.Double:
                return "double";
            case UnderLineType.SingleAccounting:
                return "singleAccounting";
            case UnderLineType.DoubleAccounting:
                return "doubleAccounting";
            default:
                return "";
        }
    }

    public class CellPosition(int row = 1, int column = 1)
    {
        public int Row { get; } = row;
        public int Column { get; } = column;

        public override bool Equals(object? obj)
        {
            if (obj is CellPosition pos)
            {
                return Row == pos.Row && Column == pos.Column;
            }

            return ReferenceEquals(this, obj);
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(Row, Column);
        }

        public static bool operator ==(CellPosition? left, CellPosition? right)
        {
            return ReferenceEquals(left, right) || 
                   (left?.Row == right?.Row && left?.Column == right?.Column);
        }

        public static bool operator !=(CellPosition? left, CellPosition? right)
        {
            return !ReferenceEquals(left, right) && 
                   (left != null && right != null && (left.Row != right.Row || left.Column != right.Column));
        }
    }

    public class Dimension
    {
        public CellPosition Start { get; set; } = new();
        public CellPosition End { get; set; } = new();
    }
}